{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 4. Custom Model\n",
    "This guide will step through the process of implementing a custom model that can be used in NeuRec. It will show example code snippets that can help build a custom model, however, further development is needed to successfully complete the model.\n",
    "\n",
    "The process of implementing a custom model involves two main steps: creating a model class; and, adding the model to NeuRec. The following sections discusses these two steps in detail.\n",
    "\n",
    "## Creating a Model Class\n",
    "This step involves creating a class in the **model** folder. This class must extend the **AbstractRecommender** class, also in the model folder, and implement the required functions, namely:\n",
    "\n",
    "* \\_\\_init\\_\\_()\n",
    "* build_graph()\n",
    "* train_model()\n",
    "* predict()\n",
    "\n",
    "> The class may have additional functions on top of these required functions.\n",
    "\n",
    "Additionally, the **properties** instance variable is required in the custom model.\n",
    "\n",
    "An example of a shell custom model is shown below:\n",
    "\n",
    "```python\n",
    "from neurec.model.AbstractRecommender import AbstractRecommender\n",
    "\n",
    "class Custom(AbstractRecommender):\n",
    "    properties = []\n",
    "    \n",
    "    def __init__(self, **kwds):\n",
    "        super().__init__(**kwds)\n",
    "        pass\n",
    "        \n",
    "    def build_graph(self):\n",
    "        pass\n",
    "        \n",
    "    def train_model(self):\n",
    "        pass\n",
    "    \n",
    "    def predict(self):\n",
    "        pass\n",
    "```\n",
    "\n",
    "The following sections will discuss each of the requirements in turn, starting with the **properties** instance variable.\n",
    "\n",
    "### properties\n",
    "This variable defines the list of properties that the model requires to run. For example:\n",
    "\n",
    "```python\n",
    "properties = [\n",
    "    \"learning_rate\",\n",
    "    \"embedding_size\",\n",
    "    \"learner\",\n",
    "    \"topk\",\n",
    "    \"epochs\",\n",
    "    \"eps\",\n",
    "    \"adv\",\n",
    "    \"adver\",\n",
    "    \"adv_epoch\",\n",
    "    \"reg\",\n",
    "    \"reg_adv\",\n",
    "    \"batch_size\",\n",
    "    \"verbose\",\n",
    "    \"loss_function\"\n",
    "]\n",
    "```\n",
    "\n",
    "These properties will then be read from the properties file when the model is instantiated, by the \\_\\_init\\_\\_() function in the AbstractRecommender class, and made available in the variable **self.conf**.\n",
    "\n",
    "### \\_\\_init\\_\\_()\n",
    "This function can be used to initialise any settings that are required by the model. It must first call the parent \\_\\_init\\_\\_() function, which will load the properties, discussed above. Additionally, it will load the dataset into **self.dataset**.\n",
    "\n",
    "```python\n",
    "from neurec.evaluation import Evaluate\n",
    "from neurec.model.AbstractRecommender import AbstractRecommender\n",
    "from neurec.util import reader, learner\n",
    "import numpy as np\n",
    "import tensorflow as tf\n",
    "\n",
    "class Custom(AbstractRecommender):\n",
    "    def __init__(self):\n",
    "        super().__init__(**kwds)\n",
    "        \n",
    "        # Load necessary settings from properties file\n",
    "        self.layers = self.conf[\"layers\"]\n",
    "        self.learning_rate = self.conf[\"learning_rate\"]\n",
    "        self.learner = self.conf[\"learner\"]\n",
    "        self.loss_function = self.conf[\"loss_function\"]\n",
    "        self.num_epochs= self.conf[\"epochs\"])\n",
    "        self.batch_size= self.conf[\"batch_size\"])\n",
    "        # etc...\n",
    "        \n",
    "    # ...\n",
    "```\n",
    "\n",
    "> If you need to log to the console, you can use the **self.logger** variable, further discussed [here](https://docs.python.org/3.6/library/logging.html)\n",
    "\n",
    "### build_graph()\n",
    "The build_graph() function is called by NeuRec before training the model and, therefore, can be used to create the network. Here, you can setup the loss and optimiser functions for the model, which can then be used while training the model:\n",
    "\n",
    "```python\n",
    "from neurec.evaluation import Evaluate\n",
    "from neurec.model.AbstractRecommender import AbstractRecommender\n",
    "from neurec.util import reader, learner\n",
    "import numpy as np\n",
    "import tensorflow as tf\n",
    "\n",
    "class Custom(AbstractRecommender):\n",
    "    # ...\n",
    "    \n",
    "    def build_graph(self):\n",
    "        self.loss = learner.pairwise_loss(self.loss_function,result) + self.reg_mf * ( tf.reduce_sum(tf.square(p1)) \\\n",
    "                + tf.reduce_sum(tf.square(q2)) + tf.reduce_sum(tf.square(q1)))\n",
    "        \n",
    "        self.optimizer = learner.optimizer(self.learner, self.loss, self.learning_rate)\n",
    "        \n",
    "    # ...\n",
    "```\n",
    "\n",
    "### train_model()\n",
    "This function is called by NeuRec to train the model. You can use the data_gen module to generate the necessary data for training, which can be found in the **util** folder. Additionally, the Evaluate class can be used here to establish the performance of the model, in this example after every epoch.\n",
    "\n",
    "```python\n",
    "from neurec.evaluation import Evaluate\n",
    "from neurec.model.AbstractRecommender import AbstractRecommender\n",
    "from neurec.util import reader, learner\n",
    "import numpy as np\n",
    "import tensorflow as tf\n",
    "\n",
    "class Custom(AbstractRecommender):\n",
    "    # ...\n",
    "    \n",
    "    def train_model(self):\n",
    "        for epoch in range(self.num_epochs):\n",
    "            user_input, item_input_pos, item_input_neg = data_gen._get_pairwise_all_data(self.dataset)\n",
    "            \n",
    "            num_training_instances = len(user_input)\n",
    "            \n",
    "            for num_batch in np.arange(int(num_training_instances/self.batch_size)):\n",
    "                bat_users, bat_items_pos, bat_items_neg = data_gen._get_pairwise_batch_data(user_input, item_input_pos, item_input_neg, num_batch, self.batch_size)\n",
    "                \n",
    "            Evaluate.test_model(self,self.dataset)\n",
    "            \n",
    "    # ...\n",
    "```\n",
    "\n",
    "### predict()\n",
    "The predict() function is used by the Evaluate classes to establish the performance of the model. An example of a predict function is given below:\n",
    "\n",
    "```python\n",
    "from neurec.evaluation import Evaluate\n",
    "from neurec.model.AbstractRecommender import AbstractRecommender\n",
    "from neurec.util import reader, learner\n",
    "import numpy as np\n",
    "import tensorflow as tf\n",
    "\n",
    "class Custom(AbstractRecommender):\n",
    "    # ...\n",
    "    \n",
    "    def predict(self):\n",
    "        users = np.full(len(items), user_id, dtype=np.int32)\n",
    "        return self.sess.run(self.output, feed_dict={self.user_input: users, self.item_input: items})\n",
    "    \n",
    "    # ...\n",
    "```\n",
    "\n",
    "## Adding to NeuRec\n",
    "After creating a custom model, NeuRec needs to be updated so that it knows the model is available. To do this, the model needs to be added to **data/models.py** as follows:\n",
    "\n",
    "```python\n",
    "import Custom\n",
    "\n",
    "models = {\n",
    "    \"custom\": Custom\n",
    "}\n",
    "```\n",
    "\n",
    "> Make sure to set the properties file to use the new model, discussed in 3. Properties"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
